﻿// -----------------------------------------------------------------------
// <copyright file="UnitDto.cs" company="JSC Centido">
//  Copyright (c) JSC Centido. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;

namespace Centido.Core.Dto
{
    /// <summary>
    ///   A data transfer object for a business unit.
    /// </summary>
    public class UnitDto : DataTransferObject
    {
        /// <summary>
        ///   The name of the xml tag that is used to store this object.
        /// </summary>
        public const string XmlTag = "unit";

        /// <summary>
        ///   The current xml schema version.
        /// </summary>
        public const string CurrentXmlSchemaVersion = "1";

        /// <summary>
        ///   The name of the xml attribute to store the unit name.
        /// </summary>
        public const string NameAttribute = "name";

        /// <summary>
        ///   The name of the xml attribute to store the unit display name
        /// </summary>
        public const string DisplayNameAttribute = "displayName";

        /// <summary>
        ///   The name of the xml tag to store the unit user list.
        /// </summary>
        public const string UserListTag = "users";

        /// <summary>
        ///   The name of the xml tag to store the unit user.
        /// </summary>
        public const string UserTag = "user";

        /// <summary>
        ///   The name of the xml attribute to store the unit user name.
        /// </summary>
        public const string UserNameAttribute = "name";

        /// <summary>
        ///   The name of the xml tag to store the child units list.
        /// </summary>
        public const string ChildUnitListTag = "children";

        /// <summary>
        ///   The name of the xml tag to store the child unit.
        /// </summary>
        public const string ChildUnitTag = "unit";

        /// <summary>
        ///   The name of the xml attribute to store the child unit name.
        /// </summary>
        public const string ChildUnitNameAttribute = "name";

        /// <summary>
        ///   The unit users.
        /// </summary>
        private readonly List<string> _userList = new List<string>();

        /// <summary>
        ///   The child units.
        /// </summary>
        private readonly List<string> _childUnitList = new List<string>();


        /// <summary>
        ///   Gets or sets the unit name.
        /// </summary>
        public string Name
        {
            get;
            set;
        }


        /// <summary>
        ///   Gets or sets the unit display name.
        /// </summary>
        public string DisplayName
        {
            get;
            set;
        }

        
        /// <summary>
        ///   Gets the unit user names.
        /// </summary>
        public ICollection<string> UserNames
        {
            get { return _userList; }
        }


        /// <summary>
        ///   Adds a user with the specified name to the unit.
        /// </summary>
        /// <param name="userName">The name of the user to be added.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="userName"/> is null.</exception>
        public void AddUser(string userName)
        {
            if (userName == null)
                throw new ArgumentNullException("userName");

            if (!_userList.Contains(userName))
                _userList.Add(userName);
        }


        /// <summary>
        ///   Deletes a user with the specified name from the unit.
        /// </summary>
        /// <param name="userName">The name of the user to be deleted.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="userName"/> is null.</exception>
        public void DeleteUser(string userName)
        {
            if (userName == null)
                throw new ArgumentNullException("userName");

            if (_userList.Contains(userName))
                _userList.Remove(userName);
        }


        /// <summary>
        ///   Gets the child units names.
        /// </summary>
        public ICollection<string> ChildUnitNames
        {
            get { return _childUnitList; }
        }


        /// <summary>
        ///   Adds a unit with the specified name as a child to the current unit.
        /// </summary>
        /// <param name="childUnitName">The name of the unit to be added.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="childUnitName"/> is null.</exception>
        public void AddChildUnit(string childUnitName)
        {
            if (childUnitName == null)
                throw new ArgumentNullException("childUnitName");

            if (!_childUnitList.Contains(childUnitName))
                _childUnitList.Add(childUnitName);
        }


        /// <summary>
        ///   Deletes a unit with the specified name from the current unit child units.
        /// </summary>
        /// <param name="childUnitName">The name of the unit to be deleted.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="childUnitName"/> is null.</exception>
        public void DeleteChildUnit(string childUnitName)
        {
            if (childUnitName == null)
                throw new ArgumentNullException("childUnitName");

            if (_childUnitList.Contains(childUnitName))
                _childUnitList.Remove(childUnitName);
        }

        /// <summary>
        ///   This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> to the class.
        /// </summary>
        /// <returns>
        ///   An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> method.
        /// </returns>
        public override XmlSchema GetSchema()
        {
            return null;
        }


        /// <summary>
        ///   Generates an object from its XML representation.
        /// </summary>
        /// <param name="reader">
        /// The <see cref="T:System.Xml.XmlReader"/> stream from which the object is deserialized. 
        /// </param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="reader"/> is null.</exception>
        public override void ReadXml(XmlReader reader)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");

            reader.MoveToContent();

            // Reading the xml schema version
            var xmlSchemaVersion = ReadStringAttribute(reader, XmlSchemaVersionTag, isRequired: false);

            // If the xml schema version is not defined
            if (string.IsNullOrEmpty(xmlSchemaVersion)
                // or if it is the current xml schema version
                || xmlSchemaVersion == CurrentXmlSchemaVersion)
            {
                // Reading the unit name
                Name = ReadStringAttribute(reader, NameAttribute);

                // Reading the unit display name
                DisplayName = ReadStringAttribute(reader, DisplayNameAttribute, isRequired: false);

                // Clearing the user list
                _userList.Clear();

                // Clearing the child unit list
                _childUnitList.Clear();

                var isEmptyElement = reader.IsEmptyElement;

                reader.ReadStartElement();

                if (!isEmptyElement)
                {
                    // Reading user and child units
                    while (reader.LocalName == UserListTag
                        || reader.LocalName == ChildUnitListTag)
                    {
                        var currentXmlTag = reader.LocalName;

                        isEmptyElement = reader.IsEmptyElement;

                        reader.ReadStartElement();

                        if (isEmptyElement) continue;

                        switch (currentXmlTag)
                        {
                            // The user list
                            case UserListTag:
                                // Reading the unit users
                                while (reader.LocalName == UserTag)
                                {
                                    AddUser(ReadStringAttribute(reader, UserNameAttribute));

                                    isEmptyElement = reader.IsEmptyElement;

                                    reader.ReadStartElement();

                                    if (!isEmptyElement) reader.ReadEndElement();
                                }
                                break;
                            // The child unit list
                            case ChildUnitListTag:
                                // Reading the child units
                                while (reader.LocalName == ChildUnitTag)
                                {
                                    AddChildUnit(ReadStringAttribute(reader, ChildUnitNameAttribute));

                                    isEmptyElement = reader.IsEmptyElement;

                                    reader.ReadStartElement();

                                    if (!isEmptyElement) reader.ReadEndElement();
                                }
                                break;
                        }

                        reader.ReadEndElement();
                    }

                    reader.ReadEndElement();
                }
            }
            else
                throw new ArgumentException(string.Format(
                    "The specified xml schema version is not supported (node = {0}, version = {1}).",
                    reader.Name,
                    xmlSchemaVersion));
        }


        /// <summary>
        ///   Converts an object into its XML representation.
        /// </summary>
        /// <param name="writer">
        ///   The <see cref="T:System.Xml.XmlWriter"/> stream to which the object is serialized. 
        /// </param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="writer"/> is null.</exception>
        public override void WriteXml(XmlWriter writer)
        {
            if (writer == null) 
                throw new ArgumentNullException("writer");

            writer.WriteStartElement(XmlTag);

            // The xml schema version
            writer.WriteAttributeString(XmlSchemaVersionTag, CurrentXmlSchemaVersion);

            // The unit name
            if (!string.IsNullOrEmpty(Name))
                writer.WriteAttributeString(NameAttribute, Name);

            // The unit display name
            if (!string.IsNullOrEmpty(DisplayName))
                writer.WriteAttributeString(DisplayNameAttribute, DisplayName);

            // The unit users
            if (_userList.Count > 0)
            {
                writer.WriteStartElement(UserListTag);

                foreach (var userName in _userList)
                {
                    writer.WriteStartElement(UserTag);

                    // The user name
                    writer.WriteAttributeString(UserNameAttribute, userName);

                    writer.WriteEndElement();
                }

                writer.WriteEndElement();
            }

            // The child units
            if (_childUnitList.Count > 0)
            {
                writer.WriteStartElement(ChildUnitListTag);

                foreach (var childUnitName in _childUnitList)
                {
                    writer.WriteStartElement(ChildUnitTag);

                    // The child unit id
                    writer.WriteAttributeString(ChildUnitNameAttribute, childUnitName);

                    writer.WriteEndElement();
                }

                writer.WriteEndElement();
            }

            writer.WriteEndElement();
        }


        /// <summary>
        ///   Copies the properties of the data transfer object to the
        /// specified unit.
        /// </summary>
        /// <param name="unit">The unit.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="unit"/> is null.</exception>
        public void CopyTo(Unit unit)
        {
            if (unit == null)
                throw new ArgumentNullException("unit");

            // The unit name
            if (Name != unit.Name)
                unit.Name = Name;

            // The unit display name
            if (DisplayName != unit.DisplayName)
                unit.DisplayName = DisplayName;
        }


        /// <summary>
        ///   Creates a data transfer object for the specified unit.
        /// </summary>
        /// <param name="unit">The specified unit.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="unit"/> is null.</exception>
        /// <returns>Returns the data transfer object for the specified unit.</returns>
        public static UnitDto CreateFrom(Unit unit)
        {
            if (unit == null)
                throw new ArgumentNullException("unit");

            var unitDto = new UnitDto
            {
                Name = unit.Name,
                DisplayName = unit.DisplayName
            };

            foreach (var user in unit.Users)
                unitDto.AddUser(user.Name);

            foreach (var childUnit in unit.ChildUnits)
                unitDto.AddChildUnit(childUnit.Name);

            return unitDto;
        }
    }
}
